/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ec;



import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.*;
import org.apache.commons.cli.*;

/**
 *
 * @author a778
 */
public class Main {
    boolean m_debug;
    boolean eofnotseen;
    FileReader file_inp;
    ProduceAssembly asm;
    String Look;
    symbol.sym_type Token;
    String Value;

    int m_line;
    List<symbol> table;
    List<symbol> keywords;

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException {

        Options options = new Options();
        options.addOption("h",false,"show usage");
        options.addOption("g", false, "enable debugging aids");
        options.addOption("c64",false,"enable c64 mode in produced assembly");
        CommandLineParser parser = new PosixParser();
        CommandLine cmd;
        

        try {
            cmd = parser.parse(options, args);
            Main instance=new Main();

            if (cmd.hasOption("h") || args.length==0){
                HelpFormatter formatter = new HelpFormatter();
                System.out.println("An ugly compiler based on Jack W. Crenshaw's \"LET'S BUILD A COMPILER\" series of articles.");
                formatter.printHelp("java -jar ec.jar file", options);
                System.exit(0);
            }
            if(cmd.hasOption("g")) {
                instance.m_debug=true;
            }
            else {
                instance.m_debug=false;
            }
            String producer;

            if (cmd.hasOption("c64")) {
                producer="c64";
            } else {
                producer="generic";

            }
            instance.createAssembly(producer);
            System.out.println(";Ugly Compiler (Versionless...still)");
            System.out.println(";Debug: "+instance.m_debug);
            System.out.println(";AssemblyProducer: "+producer);
            instance.eofnotseen=true;
            instance.Parse(cmd.getArgs()[0]);
            long time_start=System.currentTimeMillis();
            instance.Init();
            instance.TopDecls();
            instance.Block();
            long time_end=System.currentTimeMillis();
            if (time_end==time_start) ++time_end; //avoid silly division by zero
            instance.asm.Emitln(";Elapsed Time: "+(time_end-time_start)+"ms.  Source lines per second: "+instance.m_line*1000/(time_end-time_start));
        } catch (ParseException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }

        
    }

    public void createAssembly(String str)
    {
        if (str.compareTo("c64")==0)
            asm = (ProduceAssembly) new asmc64(System.out);
        else
            asm = new ProduceAssembly(System.out);

    }
    /**
     * @param filename the filename to parse
     */
    public void Parse(String filename)
    {
        m_line=0;

        table=new ArrayList<symbol>();
        keywords = new ArrayList<symbol>();
        keywords.add(new symbol("var",Token.K_DIM));
        keywords.add(new symbol("if",Token.K_IF));
        keywords.add(new symbol("else",Token.K_ELSE));
        keywords.add(new symbol("endif",Token.K_ENDIF));
        keywords.add(new symbol("while",Token.K_WHILE));
        keywords.add(new symbol("endwhile",Token.K_ENDWHILE));
        keywords.add(new symbol("print",Token.K_PRINT));
        keywords.add(new symbol("cls",Token.K_CLS));
        keywords.add(new symbol("poke",Token.K_POKE));
        keywords.add(new symbol("peek",Token.K_PEEK));
        keywords.add(new symbol("end",Token.K_END));
        keywords.add(new symbol(",",Token.COMA));
        keywords.add(new symbol("+",Token.OP_ADD));
        keywords.add(new symbol("-",Token.OP_SUB));
        keywords.add(new symbol("*",Token.OP_MUL));
        keywords.add(new symbol("/",Token.OP_DIV));
        keywords.add(new symbol("%",Token.OP_MOD));
        keywords.add(new symbol("^",Token.OP_EXP));
        keywords.add(new symbol("~",Token.OP_NOT));
        keywords.add(new symbol(">",Token.OP_GREATER));
        keywords.add(new symbol("<",Token.OP_LESS));
        keywords.add(new symbol("=",Token.OP_EQUAL));
        keywords.add(new symbol("&",Token.OP_BOOLAND));
        keywords.add(new symbol("|",Token.OP_BOOLOR));
        keywords.add(new symbol("!",Token.OP_BOOLNOT));
        keywords.add(new symbol("(",Token.LPAR));

        try {
            file_inp = new FileReader(filename);
            
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }







    public void GetChar()
    {
        int c;
        try {
            c = file_inp.read();
            if (c!=-1) {
                Look= ""+((char)c);
                if (c=='\n')
                    ++m_line;
                return;
            } else {
                eofnotseen=false;
                Look="";
                return;
            }
            

        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            System.exit(30);

        }
        Look= "";
     }

    public void Error(String str)
    {
        System.err.println("Error (line "+m_line+"): "+str);
    }

    public void Abort(String str)
    {
        System.err.println("Fatal error (line "+m_line+"): "+str);
        System.exit(20);
    }

    public void Expected(String str)
    {
        Abort(str+" expected");
    }

    public void Undefined(String str)
    {
        int l=str.length();
        Abort("Undefined identifier: "+str);
    }

    public void Duplicate(String str)
    {
        Abort("Duplicate identifier: "+str);
    }

    public void CheckIdent()
    {
        if (Token!=symbol.sym_type.VARIABLE)
            Expected("Identifier");
    }

    public void Match(String str)
    {
       if (Look.compareTo(str)!=0) {
            Expected(str);
       } else {
           GetChar();
           SkipWhite();
       }
    }

    public boolean isAlpha(String c)
    {
        c=c.toUpperCase();
        if (c.compareTo("A")>=0 &&  c.compareTo("Z")<=0)
            return true;
        return false;
    }

    public boolean isDigit(String c)
    {
        c=c.toUpperCase();
        if (c.compareTo("0")>=0 &&  c.compareTo("9")<=0)
            return true;
        return false;
    }

    public boolean isAlNum(String c)
    {
        return (isAlpha(c)||isDigit(c));
    }

    public boolean isWhite(String c)
    {
        if (c.compareTo("\t")==0 || c.compareTo(" ")==0 ||
            c.compareTo("\n")==0 || c.compareTo("\r")==0
                )
            return true;
        return false;
    }

    public void SkipWhite()
    {
        while(isWhite(Look) && eofnotseen)
            GetChar();
    }



    public int Lookup(String s)
    {
        boolean found = false;
        int i;

        for(i=0; i<table.size() && found==false; ++i)
        {
            symbol r=table.get(i);
            if (r.Value.compareTo(s)==0)
                found=true;
        }
        if (found==true)
            return i;
        else
            return -1;
    }
    
    public boolean IsKeyword(String str)
    {
        boolean ret=false;
        int i;

        for(i=0; i<keywords.size() && ret==false; ++i)
        {
            symbol r=keywords.get(i);

            if (r.Value.compareToIgnoreCase(str)==0) {
                Token=r.type;
                ret=true;
            }
        }
        return ret;
    }

    public int Locate(String str)
    {
        return Lookup(str);
    }

    public boolean InTable(String str)
    {
        return (Lookup(str)!=-1);
    }

    public void CheckTable(String str)
    {
        if (!InTable(str) && !IsKeyword(str))
            Undefined(str);
    }

    public void CheckDuplicate(String str)
    {
        if (InTable(str))
            Duplicate(str);
    }

    public void AddEntry(String str, symbol.sym_type type)
    {
        CheckDuplicate(str);
        table.add(new symbol(str,type));
    }

    public void GetName()
    {
        
        SkipWhite();
        if (!isAlpha(Look))
            Expected("Identifier");
        Token=symbol.sym_type.VARIABLE;
        Value="";
        do {
            Value+=Look;
            GetChar();
        } while(isAlNum(Look));
        IsKeyword(Value);
    }

    public void GetNum()
    {
        SkipWhite();

        if (!isDigit(Look))
            Expected("Integer");
        Value="";
        Token=symbol.sym_type.NUMBER;
        do  {
            Value+=Look;
            GetChar();
        } while(isDigit(Look));
    }

    public void GetOp()
    {
        SkipWhite();
        IsKeyword(Look);
        //Token=symbol.sym_type.OP;
        Value=Look;
        GetChar();
    }

    public void Next()
    {
        SkipWhite();
        if (isAlpha(Look))
            GetName();
        else if (isDigit(Look))
            GetNum();
        else
            GetOp();
     }

    public void Scan()
    {
        if (Token==symbol.sym_type.VARIABLE) {
            if (IsKeyword(Value))
                ;//Token=symbol.sym_type.KEYWORD;
        }
    }

    public void MatchString(String str)
    {
        if (Value.compareTo(str)!=0)
            Expected(str);
        Next();
    }


    public void Init()
    {
        GetChar();
        Next();
        //Expression();
    }

    public void Factor()
    {
        if (Token==symbol.sym_type.LPAR) {
            Next();
            BoolExpression();
            MatchString(")");
        } else {
            IsKeyword(Value);
            if (Token==symbol.sym_type.VARIABLE) {
               if (!InTable(Value) && !IsKeyword(Value))
                    Undefined(Value);
                if (InTable(Value))
                    asm.LoadVar(Value);
            }
            else if(Token==symbol.sym_type.K_PEEK)
                DoPeek();
            else if (Token==symbol.sym_type.NUMBER) {
                asm.LoadConst(Value);
                //return;
            }
            else
                Expected("Math Factor");
            Next();
        }
    }



    public void Assignment()
    {
        String Name;

        CheckTable(Value);
        Name=Value;
        Next();
        MatchString("=");
        BoolExpression();
        asm.Store(Name);
    }

    public void BoolExpression()
    {
        BoolTerm();
        //while(IsOrOp(Token)) {
        while(Token==symbol.sym_type.OP_BOOLOR || Token==symbol.sym_type.OP_BOOLXOR
                ) {
            asm.PushPrimary();
            switch(Token) {
                case OP_BOOLOR:
                    BoolOr();
                    break;
                case OP_BOOLXOR:
                    BoolXor();
                    break;

                default:
                    asm.Emitln("Weird in BoolExpression()");
            }
        }
    }

    public boolean IsOrOp(symbol.sym_type token)
    {
        if (token==symbol.sym_type.OP_BOOLOR || token==symbol.sym_type.OP_BOOLXOR)
            return true;
        return false;
    }

    public void BoolOr()
    {
        Next();
        BoolTerm();
        asm.PopOr();
    }

    public void BoolXor()
    {
        Next();
        BoolTerm();
        asm.PopXor();
    }

    public void BoolNot()
    {
        Next();
        BoolTerm();
        asm.NotIt();
    }

//    public void Negate()
//    {
//        Next();
//        BoolTerm();
//        asm.Negate();
//    }
    public void BoolTerm()
    {
        NotFactor();
        while(Token==symbol.sym_type.OP_BOOLAND) {
            asm.PushPrimary();
            Next();
            NotFactor();
            asm.PopAnd();
        }
    }

    public void NotFactor()
    {
        if (Token==symbol.sym_type.OP_BOOLNOT) {
            Next();
            Relation();
            asm.NotIt();
        } else if (Token==symbol.sym_type.OP_NOT) {
            Next();
            Relation();
            asm.Negate();
        } else
            Relation();
    }

    public void Relation()
    {
        Expression();
        if (Token==symbol.sym_type.OP_EQUAL ||
            Token==symbol.sym_type.OP_GREATER ||
            Token==symbol.sym_type.OP_LESS
            ) {
            asm.PushPrimary();
            switch (Token) {
                case OP_EQUAL:
                    Equal();
                    break;
                case OP_LESS:
                    Less();
                    break;
                case OP_GREATER:
                    Greater();
                    break;
                default:

            }
        }
    }

    public void CompareExpression()
    {
        Expression();
        asm.PopCompare();
    }
    public void NextExpression()
    {
        Next();
        CompareExpression();
    }
    public void Equal()
    {
        NextExpression();
        asm.SetEqual();
    }

    public void LessOrEqual()
    {
        NextExpression();
        asm.SetLessOrEqual();
    }

    public void NotEqual()
    {
        NextExpression();
        asm.SetNEqual();
    }

    public void ShiftLeft()
    {
        Next();
        Expression();
        asm.AsmShiftLeft();
       
    }
    public void ShiftRight()
    {
        Next();
        Expression();
        asm.AsmShiftRight();
    }
    public void Less()
    {
        Next();
        switch (Token) {
            case OP_EQUAL:
                LessOrEqual();
                break;
            case OP_GREATER:
                NotEqual();
                break;
            case OP_LESS:
                ShiftLeft();
                break;
            default:
                CompareExpression();
                asm.SetLess();
        }
    }

    public void Greater()
    {
        Next();
        if (Token==symbol.sym_type.OP_EQUAL) {
            NextExpression();
            asm.SetGreaterOrEqual();
        } else if  (Token==symbol.sym_type.OP_GREATER) {
            ShiftRight();
        }else {
            CompareExpression();
            asm.SetGreater();
        }
    }

    public void DoIf()
    {
        String l1, l2;

        Next();
        BoolExpression();
        l1=asm.NewLabel();
        l2=l1;
        asm.BranchFalse(l1);
        Block();
        if (Token==symbol.sym_type.K_ELSE) {
            Next();
            l2=asm.NewLabel();
            asm.Branch(l2);
            asm.PostLabel(l1);
            Block();
            MatchString("endif");
        }
        asm.PostLabel(l2);
    }

    public void DoWhile()
    {
        String l1,l2;

        Next();
        l1=asm.NewLabel();
        l2=asm.NewLabel();
        asm.PostLabel(l1);
        BoolExpression();
        asm.BranchFalse(l2);
        Block();
        MatchString("endwhile");
        asm.Branch(l1);
        asm.PostLabel(l2);
    }

    public void DoPrint()
    {
        Next();
        BoolExpression();
        asm.PrintPrimary();
        
    }
    public void DoCls()
    {
        asm.ClearScreen();
        Next();
    }

    public void DoPoke()
    {
        Next();
        BoolExpression();
        asm.PushPrimary();
        MatchString(",");
        BoolExpression();
        asm.Poke();
    }

    public void DoPeek()
    {
        Next();
        MatchString("(");
        BoolExpression();
        //MatchString(")");
        asm.Peek();
        
    }
    public void ReadVar()
    {
        CheckIdent();
        CheckTable(Value);
        //ReadIt(Value);
        Next();
    }

    public void DoEnd()
    {
        Next();
        asm.Emitln("rts");
        
    }
    public void Block()
    {
        Scan();
        while (Token!=symbol.sym_type.K_ENDWHILE 
                && Token!=symbol.sym_type.K_ENDIF
                && Token!=symbol.sym_type.K_ELSE
                && Token!=symbol.sym_type.K_END) {
            switch (Token) {
                case K_IF:
                    DoIf();
                    break;
                case K_WHILE:
                    DoWhile();
                    break;
                case K_PRINT:
                    DoPrint();
                    break;
                case K_CLS:
                    DoCls();
                    break;
                case K_POKE:
                    DoPoke();
                    break;
                default:
                    Assignment();
            }
            Scan();
       }
       if (Token==symbol.sym_type.K_END) DoEnd();
    }

    public void Alloc()
    {
        Next();
        if (Token!=symbol.sym_type.VARIABLE)
            Expected("Variable identifier");
        CheckDuplicate(Value);
        AddEntry(Value, symbol.sym_type.VARIABLE);
        asm.Allocate(Value, "0");
        Next();
    }

    public void TopDecls()
    {
        Scan();
        while (Token==symbol.sym_type.K_DIM) {
            Alloc();
            while (Token==symbol.sym_type.COMA)
                Alloc();
        }
    }
    public void Multiply()
    {
        Next();
        Factor();
        asm.PopMul();
    }
    public void Divide()
    {
        Next();
        Factor();
        asm.PopDiv();
    }

    public void Modulo()
    {
        Next();
        Factor();
        asm.PopMod();
    }

    public void Exponent()
    {
        Next();
        Factor();
        asm.Exp();
    }

    public void Term()
    {
        Factor();
        //while (Look.compareTo("*")==0 || Look.compareTo("/")==0)
        while(Token==symbol.sym_type.OP_MUL || Token==symbol.sym_type.OP_DIV ||
                Token==symbol.sym_type.OP_MOD || Token==symbol.sym_type.OP_EXP
             )
        {
            asm.PushPrimary();
            switch(Token) {
                case  OP_MUL:
                    Multiply();
                    break;
                case OP_DIV:
                    Divide();
                    break;
                case OP_MOD:
                    Modulo();
                    break;

                default:
                    Expected("Multiplication or division operand");
            }

        }
    }

    public void Add()
    {
       Next();
       Term();
       asm.PopAdd();
    }
    public void Subtract()
    {
       Next();
       Term();
       asm.PopSub();
    }

    public void Expression()
    {
        if (Token==symbol.sym_type.OP_ADD || Token==symbol.sym_type.OP_SUB)
            asm.Clear();
        else
            Term();
        while (Token==symbol.sym_type.OP_ADD || Token==symbol.sym_type.OP_SUB) {
            asm.PushPrimary();
            //Emitln("PHA");
             switch(Token) {
                case OP_ADD:
                    Add();
                    break;
                case OP_SUB:
                    Subtract();
                    break;
                default:
                    Expected("Addition or subtraction operand");
            }
        }
    }

    public boolean IsAddop(String str)
    {
        if (str.compareTo("+")==0 || str.compareTo("-")==0)
            return true;
        return false;
    }

    public boolean IsMulop(String str)
    {
        if (str.compareTo("*")==0 || str.compareTo("/")==0)
            return true;
        return false;
    }

//    public boolean IsOrop(String str)
//    {
//        if (str.compareTo("|")==0 || str.compareTo("~")==0)
//            return true;
//        return false;
//    }

//    public boolean IsRelop(String str)
//    {
//        if (str.compareTo("=")==0 || str.compareTo(">")==0 || str.compareTo("<")==0)
//            return true;
//        return false;
//    }

}
